### adsb_parser/adsbshark.py
import math
import time
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from parser import extract_payloads, ASSAP_OUTPUT_FILE
import threading
import asyncio
import csv
import collections
import dpkt # 用于生成PCAP格式
import socket
import queue
import numpy as np
import matplotlib.pyplot as plt
from realtime import UDPReceiver
from reply import start_pcap_replay
from datetime import datetime

class ADSBGui:
    def __init__(self, master):
        self.master = master
        self.replay_stop_flag = threading.Event()  # ⬅ 新增：回放停止控制事件
        master.title("ADS-B 数据解析工具")

        self.scale_options = [0.5, 1, 2, 5, 10, 20, 40, 80, 160, 320]  # NM
        self.current_scale_nm = tk.DoubleVar(value=20.0)

        # 设置选项卡
        self.tab_control = ttk.Notebook(master)
        self.tab_realtime = ttk.Frame(self.tab_control)
        self.tab_static = ttk.Frame(self.tab_control)
        self.tab_control.add(self.tab_realtime, text="实时接收解析")
        self.tab_control.add(self.tab_static, text="解析PCAP文件")
        self.tab_control.pack(expand=1, fill="both")

        self.create_static_tab()
        self.create_realtime_tab()
        self.replay_thread = None

    def thread_safe_log_status(self, msg, color="black"):
        # 显示当前状态消息
        self.status_bar.after(0, lambda: self.status_bar.config(text=msg, fg=color))

    def create_static_tab(self):
        static_frame = self.tab_static

        # 文件选择区域
        file_frame = tk.Frame(static_frame)
        file_frame.grid(row=0, column=0, columnspan=6, sticky="", pady=2)

        tk.Label(file_frame, text="选择PCAP文件:").pack(side="left")
        self.file_entry = tk.Entry(file_frame, width=50)
        self.file_entry.pack(side="left", padx=5)
        tk.Button(file_frame, text="浏览", command=self.browse_file).pack(side="left", padx=5)

        # 解析参数输入区域：起始索引、间隔、结束索引
        filter_frame = tk.Frame(static_frame)
        filter_frame.grid(row=1, column=0, columnspan=6, sticky="", pady=2)

        tk.Label(filter_frame, text="起始索引:").pack(side="left")
        self.start_entry = tk.Entry(filter_frame, width=8)
        self.start_entry.insert(0, "1")
        self.start_entry.pack(side="left", padx=(0, 15))

        tk.Label(filter_frame, text="间隔:").pack(side="left")
        self.step_entry = tk.Entry(filter_frame, width=8)
        self.step_entry.insert(0, "1")
        self.step_entry.pack(side="left", padx=(0, 15))

        tk.Label(filter_frame, text="结束索引:").pack(side="left")
        self.end_entry = tk.Entry(filter_frame, width=8)
        self.end_entry.pack(side="left", padx=(0, 15))

        # ✅ 添加两个复选框：用于是否启用筛选条件
        self.filter_targetsnum = tk.BooleanVar(value=True)
        self.filter_adsbvalid = tk.BooleanVar(value=True)
        tk.Checkbutton(filter_frame, text="targets_num>0", variable=self.filter_targetsnum).pack(side="left")
        tk.Checkbutton(filter_frame, text="ADSB_MSG_Validity_Num>0", variable=self.filter_adsbvalid).pack(side="left")

        # 操作按钮区域：解析、导出、统计
        action_frame = tk.Frame(static_frame)
        action_frame.grid(row=2, column=0, columnspan=6, sticky="", pady=5)

        tk.Button(action_frame, text="解析并显示数据", command=self.start_parse_thread).pack(side="left", padx=10)
        tk.Button(action_frame, text="导出CSV", command=self.export_to_csv).pack(side="left", padx=10)
        tk.Button(action_frame, text="目标数分布图", command=self.show_distribution).pack(side="left", padx=10)

        # 解析进度条和状态提示
        self.progress = ttk.Progressbar(static_frame, orient="horizontal", length=400, mode="determinate")
        self.progress.grid(row=3, column=0, columnspan=6, pady=5)

        self.progress_label = tk.Label(static_frame, text="")
        self.progress_label.grid(row=4, column=0, columnspan=6)

        # 表格列定义和展示
        self.column_names = [
            "FrameHeader", "FrameNumber", "OwnshipQualifiedForAIRB", "OwnshipQualifiedForVSA",
            "AIRB_Status", "VSA_Status", "TargetsNum", "TargetsNumOnlyTCAS",
            "TargetsNumAirborne", "TargetsNumSurface", "ADSB_MSG_Validity_Num", "FrameTail"
        ]
        self.tree = ttk.Treeview(static_frame, columns=self.column_names, show="headings")
        for col in self.column_names:
            self.tree.heading(col, text=col)
            self.tree.column(col, width=100)
        self.tree.grid(row=5, column=0, columnspan=6, sticky="nsew")
        self.tree.bind("<Double-1>", self.on_row_double_click)

        self.scrollbar = ttk.Scrollbar(static_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscroll=self.scrollbar.set)
        self.scrollbar.grid(row=5, column=6, sticky="ns")

        static_frame.grid_rowconfigure(5, weight=1)
        static_frame.grid_columnconfigure(1, weight=1)

        self.records = []

    def create_realtime_tab(self):
        frame = self.tab_realtime
        self.replay_running = False

        self.recording = False
        self.record_file = None
        self.record_start_time = None
        self.record_interval = 1
        self.record_counter = 0
        self.record_writer = None
        self.record_dir = os.path.join(os.getcwd(), "records")
        os.makedirs(self.record_dir, exist_ok=True)

        # 控制栏：接收 + 回放 + 显示距离
        ctrl_frame = tk.Frame(frame)
        ctrl_frame.pack(fill="x")

        ## 开始接收
        self.receive_button = tk.Button(ctrl_frame, text="开始接收", command=self.toggle_udp)
        self.receive_button.pack(side="left", padx=10, pady=5)

        ## 回放PCAP + 回放速度
        self.replay_button = tk.Button(ctrl_frame, text="回放PCAP", command=self.replay_pcap)
        self.replay_button.pack(side="left", padx=10)

        tk.Label(ctrl_frame, text="回放速度:").pack(side="left")
        self.replay_speed_var = tk.StringVar(value="实时 (0.1s)")
        self.replay_speed_menu = ttk.Combobox(
            ctrl_frame,
            textvariable=self.replay_speed_var,
            values=["极慢x0.1 (1s)", "特慢x0.2 (0.5s)", "慢速x0.5 (0.2s)", "实时 (0.1s)", "快速x2 (0.05s)", "特快x5 (0.02s)", "极速x10 (0.01s)"],
            width=12,
            state="readonly"
        )
        self.replay_speed_menu.pack(side="left", padx=5)

        ## 记录按钮和采样间隔
        self.record_button = tk.Button(ctrl_frame, text="开始记录", command=self.toggle_recording)
        self.record_button.pack(side="left", padx=10)

        tk.Label(ctrl_frame, text="采样间隔:").pack(side="left")
        self.record_interval_var = tk.StringVar(value="1")
        self.record_interval_entry = ttk.Entry(ctrl_frame, textvariable=self.record_interval_var, width=5,
                                               validate="key")
        self.record_interval_entry['validatecommand'] = (
        self.record_interval_entry.register(lambda v: v.isdigit() and 1 <= int(v) <= 100 or v == ""), '%P')
        self.record_interval_entry.pack(side="left")

        ## 显示距离
        tk.Label(ctrl_frame, text="显示距离:").pack(side="left")
        self.scale_menu = ttk.Combobox(ctrl_frame, values=self.scale_options, textvariable=self.current_scale_nm,
                                       width=6)
        self.scale_menu.pack(side="left")

        # 主体区域上下可调节：上部分组1+2，下部分组3
        main_pane = tk.PanedWindow(frame, orient="vertical")
        main_pane.pack(fill="both", expand=True, padx=5, pady=5)

        # 上部分组区域
        top_frame = tk.Frame(main_pane)
        # main_pane.add(top_frame, stretch="always")
        main_pane.add(top_frame, stretch="never", minsize=180)

        # 下部分组区域
        bottom_frame = tk.LabelFrame(main_pane, text="其他目标信息（分组3）", padx=5, pady=5)
        main_pane.add(bottom_frame, stretch="always")

        # 分组1 - 左上角小块：Frame Header 信息
        left_top_frame = tk.LabelFrame(top_frame, text="数据包概要（分组1）", padx=5, pady=5)
        # left_top_frame.pack(side="left", fill="both", expand=True, padx=5)
        left_top_frame.pack(side="left", fill="y", padx=5, pady=5)
        left_top_scroll = tk.Scrollbar(left_top_frame)
        left_top_scroll.pack(side="right", fill="y")
        self.summary_text = tk.Text(left_top_frame, width=35, yscrollcommand=left_top_scroll.set)
        self.summary_text.pack(fill="both", expand=True)
        left_top_scroll.config(command=self.summary_text.yview)

        # 分组2 - 右上：四栏显示 ownship 信息 和 target[0]
        right_top_frame = tk.LabelFrame(top_frame, text="本机状态（分组2）", padx=5, pady=5)
        right_top_frame.pack(side="left", fill="both", expand=True, padx=5)

        self.ownship_text = tk.Text(right_top_frame, width=40)
        self.ownship_text.pack(side="left", fill="both", expand=True)

        self.faultword_text = tk.Text(right_top_frame, width=40)
        self.faultword_text.pack(side="left", fill="both", expand=True)

        self.target0_text_left = tk.Text(right_top_frame, width=40)
        self.target0_text_left.pack(side="left", fill="both", expand=True)

        self.target0_text_right = tk.Text(right_top_frame, width=40)
        self.target0_text_right.pack(side="left", fill="both", expand=True)

        # 分组3 - 下方：地图+表格，地图为动态方形，右边是目标列表
        map_list_frame = tk.PanedWindow(bottom_frame, orient="horizontal")
        map_list_frame.pack(fill="both", expand=True)

        # 方形雷达图，自适应大小
        self.canvas_frame = tk.Frame(map_list_frame)
        self.canvas_frame.pack(fill="both", expand=True)
        self.canvas = tk.Canvas(self.canvas_frame, bg="black")
        self.canvas.pack(fill="both", expand=True)
        map_list_frame.add(self.canvas_frame, minsize=500)

        # 滚动表格区域
        table_frame = tk.Frame(map_list_frame)
        xscroll = ttk.Scrollbar(table_frame, orient="horizontal")
        xscroll.pack(side="bottom", fill="x")
        yscroll = ttk.Scrollbar(table_frame, orient="vertical")
        yscroll.pack(side="right", fill="y")

        self.target_list = ttk.Treeview(table_frame,
                                        columns=("时间/s", "类型", "TCAS告警状态", "入侵机SL", "TCAS Tag", "空地状态",
                                                 "S模式地址", "Unique ID", "Flight ID", "发射机类别", "紧急优先状态",
                                                 "LAT/deg", "LON/deg", "ALT/ft", "距离/nm", "方位/deg", "相对高度/ft",
                                                 "东西南北速度有效性", "NS Vel/kts", "EW Vel/kts", "交通方向有效性",
                                                 "HDG_TRK/deg", "GS/kts", "DGS", "垂直感知", "垂直速度/ft/min",
                                                 "交通质量满足VSA", "NACp", "NACv", "选中标志", "选中VSA标志",
                                                 "相对高度有效性", "方位有效性", "功能测试激活", "垂直速度类型",
                                                 "几何垂直速度有效性", "气压垂直速度有效性"), show="headings",
                                        xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
        for col in self.target_list["columns"]:
            self.target_list.heading(col, text=col)
            self.target_list.column(col, width=120, anchor="center")
        self.target_list.pack(side="left", fill="both", expand=True)

        xscroll.config(command=self.target_list.xview)
        yscroll.config(command=self.target_list.yview)
        map_list_frame.add(table_frame)

        # 绑定画布大小调整事件以强制重绘
        self.canvas.bind("<Configure>", lambda event: self.redraw_radar())

        # 添加状态栏
        # self.status_bar = tk.Label(frame, text="Ready", bd=1, relief=tk.SUNKEN, anchor="w", bg="black", fg="white")
        self.status_bar = tk.Label(frame, text="Ready", bd=1, relief=tk.SUNKEN, anchor="w")
        # self.status_bar = tk.Label(self.tab_realtime, text="Ready", bd=1, relief=tk.SUNKEN, anchor="w")
        self.status_bar.pack(side="bottom", fill="x")

        # 初始化 UDP 和回放控制器
        self.replay_thread = None
        self.replay_stop_flag = threading.Event()
        self.udp_queue = queue.Queue()
        self.udp_receiver = UDPReceiver(self.udp_queue, logger=self.thread_safe_log_status,  raw_logger=self.record_raw_udp)
        self.master.after(200, self.update_realtime_display)

    def toggle_recording(self):
        if not self.recording:
            # 读取采样间隔
            try:
                interval = int(self.record_interval_var.get())
                if not (1 <= interval <= 100):
                    raise ValueError
                self.record_interval = interval
            except ValueError:
                self.thread_safe_log_status("采样间隔必须是 1 到 100 的整数", color="red")
                return

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.record_file_path = os.path.join(self.record_dir, f"udp_record_{timestamp}.pcap")
            self.record_file = open(self.record_file_path, "wb")
            self.record_pcap_writer = dpkt.pcap.Writer(self.record_file)
            self.record_start_time = time.time()
            self.record_counter = 0
            self.recording = True
            self.record_button.config(text="停止记录")
            self.thread_safe_log_status(f"开始记录UDP数据: {self.record_file_path}")
        else:
            self.recording = False
            if self.record_file:
                self.record_file.close()
            self.record_file = None
            self.record_pcap_writer = None
            self.thread_safe_log_status("UDP数据记录已停止")
            self.record_button.config(text="开始记录")

    def stop_pcap_replay(self):
        if self.replay_thread and self.replay_thread.is_alive():
            self.replay_stop_flag.set()
            self.thread_safe_log_status("已请求停止回放", color="orange")

    def redraw_radar(self):
        self.canvas.delete("all")
        w = self.canvas.winfo_width()
        h = self.canvas.winfo_height()
        size = min(w * 0.9, h * 0.85)  # 给上方 HDG 留空间
        cx = w // 2
        cy = h // 2 + 20
        max_nm = self.current_scale_nm.get()
        scale = size / 2 # 半径

        # 最外圈距离环
        self.canvas.create_oval(cx - scale, cy - scale, cx + scale, cy + scale, outline="gray", width=2)
        self.canvas.create_text(cx + scale * math.cos(3 / 4 * math.pi), cy + scale * math.sin(3 / 4 * math.pi), text=f"{max_nm:.0f}NM", fill="white", anchor="w")

        # 中圈距离环（50%）
        mid_scale = scale * 0.5
        self.canvas.create_oval(cx - mid_scale, cy - mid_scale, cx + mid_scale, cy + mid_scale, outline="gray", dash=(4, 2), width=2)
        self.canvas.create_text(cx + mid_scale * math.cos(3 / 4 * math.pi), cy + mid_scale * math.sin(3 / 4 * math.pi), text=f"{max_nm / 2:.0f}NM", fill="white", anchor="w")

        # 0°航向线
        self.canvas.create_line(cx, cy+scale, cx, cy - scale, fill="green", width=2)

        # 航向值显示框
        box_w, box_h = 80, 20
        box_y = cy - scale - box_h - 10
        self.canvas.create_rectangle(cx - box_w // 2, box_y, cx + box_w // 2, box_y + box_h, fill="white")
        try:
            hdg = self.latest_heading / math.pi * 180
            self.canvas.create_text(cx, box_y + box_h // 2, text=f"HDG: {hdg:.1f}°", fill="black")
        except:
            self.canvas.create_text(cx, box_y + box_h // 2, text="HDG: ---", fill="black")

        # 外圈角度刻度
        try:
            hdg = self.latest_heading
            for theta in np.arange(0, 2*math.pi, math.pi/6):
                r_outer = scale + 10
                r_inner = scale

                x1 = cx + r_outer * math.sin(theta - hdg)
                y1 = cy - r_outer * math.cos(theta - hdg)
                x2 = cx + r_inner * math.sin(theta - hdg)
                y2 = cy - r_inner * math.cos(theta - hdg)

                self.canvas.create_line(x1, y1, x2, y2, fill="white", width=2)
                self.canvas.create_text(x1, y1, text=f"{theta/math.pi*180:.0f}", fill="white")
        except:
            for theta in np.arange(0, 2*math.pi, math.pi/6):
                r_outer = scale + 10
                r_inner = scale

                x1 = cx + r_outer * math.sin(theta)
                y1 = cy - r_outer * math.cos(theta)
                x2 = cx + r_inner * math.sin(theta)
                y2 = cy - r_inner * math.cos(theta)

                self.canvas.create_line(x1, y1, x2, y2, fill="white", width=2)

        # 圆心本机，大小根据雷达图半径变化
        tri_height = scale * 0.1
        tri_base = tri_height / 2
        # 三角形本机符号
        # points = [
        #     cx, cy - tri_height / 2,  # 顶点（上）
        #         cx - tri_base / 2, cy + tri_height / 2,  # 左底角
        #         cx + tri_base / 2, cy + tri_height / 2  # 右底角
        # ]
        # self.canvas.create_polygon(points, fill="white")
        # 士字形本机符号
        self.canvas.create_line(cx, cy + tri_height, cx, cy - tri_height, fill="yellow", width=2)
        self.canvas.create_line(cx + tri_height, cy, cx - tri_height, cy, fill="yellow", width=2)
        self.canvas.create_line(cx + tri_height / 3, cy + tri_height, cx - tri_height / 3, cy + tri_height, fill="yellow", width=2)

    def replay_pcap(self):
        if self.replay_running:
            self.replay_stop_flag.set()
            self.replay_running = False
            self.replay_button.config(text="回放PCAP")
            self.thread_safe_log_status("已请求停止回放", color="red")
            return

        filepath = filedialog.askopenfilename(filetypes=[("PCAP 文件", "*.pcap")])
        if not filepath:
            return

        # 动态速率映射
        speed_map = {
            "极慢x0.1 (1s)": 1,
            "特慢x0.2 (0.5s)": 0.5,
            "慢速x0.5 (0.2s)": 0.2,
            "实时 (0.1s)": 0.1,
            "快速x2 (0.05s)": 0.05,
            "特快x5 (0.02s)": 0.02,
            "极速x10 (0.01s)": 0.01
        }
        delay_val = speed_map.get(self.replay_speed_var.get(), 0.1)

        self.replay_stop_flag.clear()
        self.replay_thread = threading.Thread(
            target=start_pcap_replay,
            args=(filepath,),  # 把 filepath 放到 args 中，不要放 kwargs
            kwargs={
                "target_ip": '127.0.0.1',
                "target_port": 62325,
                "delay": delay_val,
                "stop_flag": self.replay_stop_flag,
                "logger": self.thread_safe_log_status
            },
            daemon=True
        )
        self.replay_thread.start()
        self.replay_running = True
        self.replay_button.config(text="停止回放")
        self.thread_safe_log_status(f"已开始回放: {os.path.basename(filepath)}", color="cyan")

    def toggle_udp(self):
        if self.udp_receiver.running:
            self.udp_receiver.stop()
            self.receive_button.config(text="开始接收")
        else:
            self.udp_receiver.start()
            self.receive_button.config(text="停止接收")

    def record_raw_udp(self, raw_bytes):
        if not self.recording:
            return
        self.record_counter += 1
        if self.record_counter >= self.record_interval:
            self.record_counter = 0
            ts = time.time()
            try:
                # 构造伪IP/UDP头部（仿Wireshark格式）
                udp = dpkt.udp.UDP(
                    sport=62325,
                    dport=62325,
                    data=raw_bytes
                )
                udp.ulen = len(udp)
                udp.pack()

                ip = dpkt.ip.IP(
                    src=socket.inet_aton("127.0.0.1"),
                    dst=socket.inet_aton("127.0.0.1"),
                    p=dpkt.ip.IP_PROTO_UDP,
                    ttl=64,
                    data=udp
                )
                ip.len = len(ip)
                ip.pack()

                eth = dpkt.ethernet.Ethernet(
                    dst=b'\xaa\xaa\xaa\aa\aa\aa',
                    src=b'\xbb\xbb\xbb\bb\bb\bb',
                    type=dpkt.ethernet.ETH_TYPE_IP,
                    data=ip
                )
                self.record_pcap_writer.writepkt(eth.pack(), ts)
            except Exception as e:
                self.thread_safe_log_status(f"写入PCAP失败: {e}", color="red")

            if time.time() - self.record_start_time >= 3600:
                self.record_file.close()
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                self.record_file_path = os.path.join(self.record_dir, f"udp_record_{timestamp}.pcap")
                self.record_file = open(self.record_file_path, "wb")
                self.record_pcap_writer = dpkt.pcap.Writer(self.record_file)
                self.record_start_time = time.time()
                self.thread_safe_log_status("记录已保存为新文件")

    def record_summary_and_ownship(self, parsed):
        if not parsed:
            return
        if self.record_writer is None:
            fields = [
                "FrameHeader", "FrameNumber", "ownship_qualified_for_airb", "ownship_qualified_for_vsa",
                "targets_num", "targets_num_only_tcas", "targets_num_airborne", "targets_num_surface",
                "ADSB_MSG_Validity_Num", "FrameTail"
            ]
            self.record_writer = csv.DictWriter(self.record_file, fieldnames=fields)
            self.record_writer.writeheader()

        record_row = {
            "FrameHeader": parsed.get("FrameHeader"),
            "FrameNumber": parsed.get("FrameNumber"),
            "ownship_qualified_for_airb": parsed.get("ownship_qualified_for_airb"),
            "ownship_qualified_for_vsa": parsed.get("ownship_qualified_for_vsa"),
            "targets_num": parsed.get("targets_num"),
            "targets_num_only_tcas": parsed.get("targets_num_only_tcas"),
            "targets_num_airborne": parsed.get("targets_num_airborne"),
            "targets_num_surface": parsed.get("targets_num_surface"),
            "ADSB_MSG_Validity_Num": parsed.get("ADSB_MSG_Validity_Num"),
            "FrameTail": parsed.get("FrameTail")
        }
        self.record_writer.writerow(record_row)

        # 每小时更换文件，关闭旧文件，打开新文件
        if time.time() - self.record_start_time >= 3600:
            self.record_file.close()
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.record_file_path = os.path.join(self.record_dir, f"record_{timestamp}.csv")
            self.record_file = open(self.record_file_path, "w", newline='', encoding='utf-8')
            self.record_writer = None
            self.record_start_time = time.time()
            self.thread_safe_log_status("记录已保存为新文件")

    def update_realtime_display(self):
        try:
            while not self.udp_queue.empty():
                data = self.udp_queue.get()
                parsed = ASSAP_OUTPUT_FILE.parse(data)
                self.update_summary(parsed)
                self.update_ownship(parsed)
                self.update_targets(parsed)

                # 记录CSV，当前记录已经不在这里处理了，所以不再需要了
                # if self.recording:
                #     self.record_counter += 1
                #     if self.record_counter >= self.record_interval:
                #         self.record_counter = 0
                #         self.record_summary_and_ownship(parsed)

        except Exception as e:
            self.thread_safe_log_status(f"实时解析错误：{e}", color="red")
        self.master.after(200, self.update_realtime_display)

    def update_summary(self, parsed):
        self.summary_text.delete("1.0", tk.END)
        self.summary_text.insert(tk.END, f"单包字节数: {ASSAP_OUTPUT_FILE.sizeof()}\n")
        self.summary_text.insert(tk.END, f"FrameHeader: 0x{parsed.FrameHeader:08X}\n")
        self.summary_text.insert(tk.END, f"FrameNumber: {parsed.FrameNumber}\n")
        self.summary_text.insert(tk.END, f"OwnshipQualifiedForAIRB: {parsed.ownship_qualified_for_airb}\n")
        self.summary_text.insert(tk.END, f"OwnshipQualifiedForVSA: {parsed.ownship_qualified_for_vsa}\n")
        self.summary_text.insert(tk.END, f"AIRB_Status: {parsed.airb_status}\n")
        self.summary_text.insert(tk.END, f"VSA_Status: {parsed.vsa_status}\n")
        self.summary_text.insert(tk.END, f"TargetsNum: {parsed.targets_num}\n")
        self.summary_text.insert(tk.END, f"TargetsNumOnlyTCAS: {parsed.targets_num_only_tcas}\n")
        self.summary_text.insert(tk.END, f"TargetsNumAirborne: {parsed.targets_num_airborne}\n")
        self.summary_text.insert(tk.END, f"TargetsNumSurface: {parsed.targets_num_surface}\n")
        self.summary_text.insert(tk.END, f"ADSB_MSG_Validity_Num: {parsed.ADSB_MSG_Validity_Num}\n")
        self.summary_text.insert(tk.END, f"FrameTail: 0x{parsed.FrameTail:08X}\n")

    def update_ownship(self, parsed):
        self.ownship_text.config(state="normal")
        self.ownship_text.delete("1.0", tk.END)
        self.faultword_text.delete("1.0", tk.END)
        self.target0_text_left.delete("1.0", tk.END)
        self.target0_text_right.delete("1.0", tk.END)

        # def format_container(container, indent=0):
        #     lines = []
        #     for k, v in container.items():
        #         if k == '_io':
        #             continue
        #         if hasattr(v, 'items'):
        #             lines.append(" " * indent + f"{k}:")
        #             lines.extend(format_container(v, indent + 2))
        #         else:
        #             lines.append(" " * indent + f"{k}: {v}")
        #     return lines

        def insert_struct_fields(text_widget, struct, title="", key_width=35, val_width=5, exclude_fields=None):
            """
            自动将结构体的所有字段以对齐格式写入到给定文本控件中。
            - text_widget: tk.Text 控件
            - struct: Container 或任意对象（支持 .__dict__ 或 dict-like）
            - title: 可选标题（加粗/变色）
            - exclude_fields: 可选字段名列表，排除显示
            """
            if title:
                text_widget.insert(tk.END, f"[ {title} ]\n", ("title",))

            # 兼容 dict、Container、Struct 等类型
            try:
                fields = struct.items() if isinstance(struct, dict) else vars(struct).items()
            except TypeError:
                fields = [(k, getattr(struct, k)) for k in dir(struct) if not k.startswith("_")]

            if exclude_fields:
                fields = [(k, v) for k, v in fields if k not in exclude_fields and k != "_io"]

            for k, v in fields:
                if k == "_io":
                    continue  # 跳过 _io 字段

                # 格式化浮点数：保留 3 位小数
                if isinstance(v, float):
                    val_str = f"{v:.3f}"
                else:
                    val_str = str(v)

                text_widget.insert(tk.END, f"{k:<{key_width}} = {val_str:>{val_width}}\n")

        # 设置文本样式 tag
        for widget in [self.ownship_text, self.faultword_text, self.target0_text_left, self.target0_text_right]:
            widget.tag_configure("title", foreground="blue", font=("Courier", 10, "bold"))

        tmp1 = parsed.ownship_status_infomation
        insert_struct_fields(self.ownship_text, tmp1.bitfield1, title="Ownship Bitfield1")
        insert_struct_fields(self.ownship_text, tmp1.bitfield2, title="Ownship Bitfield2")
        insert_struct_fields(self.faultword_text, tmp1.ADSB_Fault_Status, title="ADSB Fault Status")
        # sw_number 单独打印
        sw_text = tmp1.sw_number.decode(errors='ignore').strip(chr(0))
        self.ownship_text.insert(tk.END, f"{'sw_number':<35} = {sw_text}\n")

        # lines = format_container(parsed.ownship_status_infomation)
        # mid0 = 8
        # for line in lines[:mid0]:
        #     indent = len(line) - len(line.lstrip())
        #     if indent >= 2:
        #         self.ownship_text.insert(tk.END, line + "\n", "nested")
        #     else:
        #         self.ownship_text.insert(tk.END, line + "\n", "group")
        # for line in lines[mid0:]:
        #     indent = len(line) - len(line.lstrip())
        #     if indent >= 2:
        #         self.faultword_text.insert(tk.END, line + "\n", "nested")
        #     else:
        #         self.faultword_text.insert(tk.END, line + "\n", "group")
        #
        # # 设置 tag 样式
        # self.ownship_text.tag_config("group", foreground="blue", font=("TkDefaultFont", 10, "bold"))
        # self.ownship_text.tag_config("nested", foreground="darkgreen")
        # # self.ownship_text.config(state="disabled")
        # self.faultword_text.tag_config("group", foreground="blue", font=("TkDefaultFont", 10, "bold"))
        # self.faultword_text.tag_config("nested", foreground="darkgreen")
        # # self.ownship_text.config(state="disabled")

        # targets[0] 分两栏显示（添加分组颜色）
        tgt0 = parsed.targets[0]
        insert_struct_fields(self.target0_text_left, tgt0, title="Ownship Target Element", exclude_fields=["bitfield1", "bitfield2", "bitfield3",
                                                                                                           "bitfield1_raw", "bitfield2_raw", "bitfield3_raw"])
        insert_struct_fields(self.target0_text_right, tgt0.bitfield1, title="Target Bitfield1")
        insert_struct_fields(self.target0_text_right, tgt0.bitfield2, title="Target Bitfield2")
        insert_struct_fields(self.target0_text_right, tgt0.bitfield3, title="Target Bitfield3")

        # lines1 = format_container(parsed.targets[0])
        # mid = 24
        # for line1 in lines1[:mid]:
        #     indent = len(line1) - len(line1.lstrip())
        #     if indent >= 2:
        #         self.target0_text_left.insert(tk.END, line1 + "\n", "nested")
        #     else:
        #         self.target0_text_left.insert(tk.END, line1 + "\n", "group")
        # for line1 in lines1[mid:]:
        #     indent = len(line1) - len(line1.lstrip())
        #     if indent >= 2:
        #         self.target0_text_right.insert(tk.END, line1 + "\n", "nested")
        #     else:
        #         self.target0_text_right.insert(tk.END, line1 + "\n", "group")
        #
        # # 设置 tag 样式
        # self.target0_text_left.tag_config("group", foreground="blue", font=("TkDefaultFont", 10, "bold"))
        # self.target0_text_left.tag_config("nested", foreground="darkgreen")
        # # self.target0_text_left.config(state="enabled")
        # self.target0_text_right.tag_config("group", foreground="blue", font=("TkDefaultFont", 10, "bold"))
        # self.target0_text_right.tag_config("nested", foreground="darkgreen")
        # # self.target0_text_left.config(state="enabled")

    def update_targets(self, parsed):
        self.latest_heading = getattr(parsed.targets[0], "track_hdg_trk", 0.0)  # 提供航向值供 redraw_radar()

        self.target_list.delete(*self.target_list.get_children())

        # 添加它机目标到列表（不受雷达缩放影响）
        for t in parsed.targets[1:parsed.targets_num]:
            self.target_list.insert("", "end", values=(t.bintime,
                                                       t.type,
                                                       t.tcas_alert_status,
                                                       t.bitfield2.intruder_sensitivity_level,
                                                       t.bitfield2.tcas_tag,
                                                       t.bitfield1.air_gnd_status,
                                                       t.num,
                                                       t.target_unique_id,
                                                       t.flight_id,
                                                       t.emitter_cat,
                                                       t.bitfield1.emergency_priority_status,
                                                       f"{t.lat/math.pi*180:.3f}",
                                                       f"{t.lon/math.pi*180:.3f}",
                                                       f"{t.alt*3.2808:.3f}",
                                                       f"{t.range*0.00053996:.3f}",
                                                       f"{t.bearing/math.pi*180:.3f}",
                                                       f"{t.relative_alt*3.2808:.3f}",
                                                       t.bitfield3.ew_ns_velocity_validity,
                                                       f"{t.ns_v*1.9438:.3f}",
                                                       f"{t.ew_v*1.9438:.3f}",
                                                       t.bitfield2.validity_flag_traffic_dir,
                                                       f"{t.track_hdg_trk/math.pi*180:.3f}",
                                                       f"{t.ground_speed*1.9438:.3f}",
                                                       t.dgs,
                                                       t.bitfield2.vertical_sense,
                                                       f"{t.vertical_speed*196.8503:.3f}",
                                                       t.bitfield2.traffic_qualified_for_vsa,
                                                       t.bitfield2.nacp,
                                                       t.bitfield2.nacv,
                                                       t.bitfield2.selection_mark,
                                                       t.bitfield2.selection_vsa_mark,
                                                       t.bitfield2.relative_alt_validity,
                                                       t.bitfield2.bearing_validity,
                                                       t.bitfield2.function_test_active,
                                                       t.bitfield2.vertical_speed_type,
                                                       t.bitfield2.geo_vertical_speed_validity,
                                                       t.bitfield2.baro_vertical_speed_validity,
                                                       ))
        # 画图（雷达目标）
        self.redraw_radar()
        for t in parsed.targets[1:parsed.targets_num]:
            rng = t.range # 单位：米
            brg = t.bearing # 单位：弧度
            max_range_nm = self.current_scale_nm.get()
            max_range_m = max_range_nm * 1852  # 转换为米

            if rng > max_range_m:
                continue

            # canvas尺寸获取
            w = self.canvas.winfo_width()
            h = self.canvas.winfo_height()
            size = min(w * 0.9, h * 0.85)  # radar图直径（像素）
            cx = w // 2
            cy = h // 2 + 20
            canvas_radius_px = size / 2

            # 计算缩放比例（像素/米）
            scale = canvas_radius_px / max_range_m

            # 计算目标在画布上的坐标
            r_px = rng * scale
            rad = brg
            x = cx + r_px * math.sin(rad)
            y = cy - r_px * math.cos(rad)

            alert_text_TA = None
            alert_text_RA = None
            symbol_text = None
            flight_id = t.flight_id.decode(errors="ignore").strip("\x00") if hasattr(t, "flight_id") else ""
            track_angle = getattr(t, "track_hdg_trk", 0)

            def draw_chevron(x, y, color, filled=True, angle=0, pix=10):
                size = pix
                half = size / 2
                points = [
                    (x, y - size),
                    (x - half, y + half),
                    (x, y),
                    (x + half, y + half)
                ]
                # 旋转
                rotated = []
                for px, py in points:
                    dx, dy = px - x, py - y
                    new_x = x + dx * math.cos(angle) - dy * math.sin(angle)
                    new_y = y + dx * math.sin(angle) + dy * math.cos(angle)
                    rotated.append((new_x, new_y))
                flat = [coord for pt in rotated for coord in pt]
                if filled:
                    self.canvas.create_polygon(flat, fill=color, outline=color, width=2)
                else:
                    self.canvas.create_polygon(flat, fill="", outline=color, width=2)

            def draw_diamond(x, y, color, filled, pix=8):
                size = pix
                points = [
                    (x, y - size),
                    (x + size, y),
                    (x, y + size),
                    (x - size, y)
                ]
                if filled:
                    self.canvas.create_polygon(points, fill=color, outline=color, width=2)
                else:
                    self.canvas.create_polygon(points, fill="", outline=color, width=2)

            def draw_circle(x, y, color, pix=6):
                r = pix
                self.canvas.create_oval(x - r, y - r, x + r, y + r, fill=color, outline=color, width=2)

            def draw_square(x, y, color, pix=6):
                size = pix
                self.canvas.create_rectangle(x - size, y - size, x + size, y + size, fill=color, outline=color, width=2)

            # =============== 判定逻辑 ===============
            type = t.type
            alert = t.tcas_alert_status
            tag = t.bitfield2.tcas_tag
            dir_valid = t.bitfield2.validity_flag_traffic_dir

            if type == 4:
                # 纯 TCAS
                if alert == 0: # OT
                    draw_diamond(x, y, color="white", filled=False)  # ID 1
                elif alert == 1: # PT
                    draw_diamond(x, y, color="white", filled=True)  # ID 2
                elif alert == 2: # TA
                    draw_circle(x, y, color="yellow")  # ID 3
                    alert_text_TA = "TRAFFIC"
                elif alert == 4: # RA
                    draw_square(x, y, color="red")  # ID 4
                    alert_text_RA = "TRAFFIC"

            elif type in [1, 2, 3]:
                if alert == 0: # OT
                    if dir_valid == 0:
                        if tag == 0:
                            draw_diamond(x, y, color="white", filled=False)  # ID 1
                        else:
                            draw_diamond(x, y, color="white", filled=False)  # ID 1
                            symbol_text = "TCAS"
                    else:
                        if tag == 0:
                            draw_chevron(x, y, color="white", filled=False, angle= track_angle - self.latest_heading, pix=12)  # ID 6
                        else:
                            draw_chevron(x, y, color="white", filled=False, angle= track_angle - self.latest_heading, pix=12)  # ID 6
                            symbol_text = "TCAS"
                elif alert == 1: # PT
                    if dir_valid == 0:
                        if tag == 0:
                            draw_diamond(x, y, color="white", filled=True)  # ID 2
                        else:
                            draw_diamond(x, y, color="white", filled=True)  # ID 2
                            symbol_text = "TCAS"
                    else:
                        if tag == 0:
                            draw_chevron(x, y, color="white", filled=True, angle= track_angle - self.latest_heading, pix=12)  # ID 7
                        else:
                            draw_chevron(x, y, color="white", filled=True, angle= track_angle - self.latest_heading, pix=12)  # ID 7
                            symbol_text = "TCAS"
                elif alert == 2: # TA
                    if dir_valid == 0:
                        if tag == 0:
                            draw_circle(x, y, color="yellow")  # ID 3
                        else:
                            draw_circle(x, y, color="yellow")  # ID 3
                            symbol_text = "TCAS"
                    else:
                        if tag == 1:
                            draw_circle(x, y + 10, color="yellow", pix=7)  # ID 3（下方）
                            draw_chevron(x, y + 10, color="black", filled=True, angle= track_angle - self.latest_heading, pix=10)  # ID 5（上方）
                            symbol_text = "TCAS"
                    alert_text_TA = "TRAFFIC"
                elif alert == 4: # RA
                    if dir_valid == 0:
                        if tag == 0:
                            draw_square(x, y, color="red")  # ID 4
                        else:
                            draw_square(x, y, color="red")
                            symbol_text = "TCAS"
                    else:
                        if tag == 1:
                            draw_square(x, y + 10, color="red", pix=7)  # ID 4（下方）
                            draw_chevron(x, y + 10, color="black", filled=True, angle= track_angle - self.latest_heading, pix=10)  # ID 5（上方）
                            symbol_text = "TCAS"
                    alert_text_RA = "TRAFFIC"

            # 额外绘制 TCAS 字样
            if symbol_text:
                self.canvas.create_text(x + 12, y - 12, text=symbol_text, fill="cyan", font=("Arial", 8, "bold"))

            if flight_id:
                self.canvas.create_text(x, y + 14, text=flight_id, fill="white", font=("Arial", 8))

            # 额外绘制告警字样
            if alert_text_TA:
                if alert_text_RA: # 既有TA又有RA
                    self.canvas.create_text(0, 0, text=alert_text_RA, fill="red", font=("Arial", 8))
                else: # 只有TA
                    self.canvas.create_text(0, 0, text=alert_text_TA, fill="yellow", font=("Arial", 8))
            elif alert_text_RA: # 只有RA
                self.canvas.create_text(0, 0, text=alert_text_RA, fill="red", font=("Arial", 8))

    def browse_file(self):
        filepath = filedialog.askopenfilename(filetypes=[("PCAP files", "*.pcap"), ("All files", "*.*")])
        if filepath:
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, filepath)

    def start_parse_thread(self):
        threading.Thread(target=self.parse_and_display, daemon=True).start()

    def parse_and_display(self):
        asyncio.set_event_loop(asyncio.new_event_loop())

        pcap_path = self.file_entry.get()
        if not pcap_path:
            messagebox.showwarning("提示", "请先选择PCAP文件")
            return

        try:
            start_index = int(self.start_entry.get())
            step = int(self.step_entry.get())
            end_text = self.end_entry.get()
            end_index = None if end_text.strip() == '' else int(end_text.strip())
            if start_index < 0 or step <= 0:
                raise ValueError
        except ValueError:
            messagebox.showerror("错误", "起始索引、间隔必须为正整数，结束索引可为空或正整数")
            return

        try:
            self.progress["value"] = 0
            self.progress_label.config(text="正在加载数据包...")
            self.tree.delete(*self.tree.get_children())
            self.records.clear()

            all_payloads = list(extract_payloads(pcap_path))
            total = len(all_payloads)
            if total == 0:
                messagebox.showinfo("提示", "未发现符合条件的数据包")
                return
            if end_index is None or end_index > total:
                end_index = total

            selected_indices = list(range(start_index, end_index, step))
            selected_total = len(selected_indices)

            for idx, i in enumerate(selected_indices):
                if i >= total:
                    break
                payload = all_payloads[i]
                try:
                    parsed = ASSAP_OUTPUT_FILE.parse(payload)
                    # ✅ 结合复选框过滤条件（targets_num, ADSB_MSG_Validity_Num）
                    if (not self.filter_targetsnum.get() or parsed.targets_num > 0) and \
                            (not self.filter_adsbvalid.get() or parsed.ADSB_MSG_Validity_Num > 0):
                        self.records.append(parsed)
                        self.tree.insert("", tk.END, values=(
                            f"0x{parsed.FrameHeader:08X}", parsed.FrameNumber,
                            parsed.ownship_qualified_for_airb, parsed.ownship_qualified_for_vsa,
                            parsed.airb_status, parsed.vsa_status,
                            parsed.targets_num, parsed.targets_num_only_tcas,
                            parsed.targets_num_airborne, parsed.targets_num_surface,
                            parsed.ADSB_MSG_Validity_Num, f"0x{parsed.FrameTail:08X}"
                        ))
                except Exception as e:
                    self.thread_safe_log_status(f"实时解析错误：{e}", color="red")
                self.progress["value"] = (idx + 1) / selected_total * 100
                self.progress_label.config(text=f"正在解析第 {idx + 1} / {selected_total} 条")
                self.master.update_idletasks()

            self.progress_label.config(text="解析完成")
            messagebox.showinfo("完成", f"成功解析 {len(self.records)} 个数据包（总共 {total} 个，间隔 {step}）")
        except Exception as e:
            self.progress_label.config(text="解析失败")
            messagebox.showerror("错误", f"解析失败: {str(e)}")

    def export_to_csv(self):
        # 导出当前记录为CSV文件
        if not self.records:
            messagebox.showwarning("提示", "没有数据可导出")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV 文件", "*.csv")])
        if not file_path:
            return

        try:
            with open(file_path, "w", newline='') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerow(self.column_names)
                for r in self.records:
                    writer.writerow([
                        f"0x{r.FrameHeader:08X}", r.FrameNumber,
                        r.ownship_qualified_for_airb, r.ownship_qualified_for_vsa,
                        r.airb_status, r.vsa_status,
                        r.targets_num, r.targets_num_only_tcas,
                        r.targets_num_airborne, r.targets_num_surface,
                        r.ADSB_MSG_Validity_Num, f"0x{r.FrameTail:08X}"
                    ])
            messagebox.showinfo("导出完成", f"CSV 已保存至：{file_path}")
        except Exception as e:
            messagebox.showerror("错误", f"导出失败: {str(e)}")

    def show_distribution(self):
        # 显示目标数分布图
        if not self.records:
            messagebox.showwarning("提示", "没有数据可统计")
            return
        try:
            counter = collections.Counter([r.targets_num for r in self.records])
            x = list(counter.keys())
            y = list(counter.values())
            plt.bar(x, y)
            plt.title("TargetsNum 分布统计")
            plt.xlabel("TargetsNum")
            plt.ylabel("数量")
            plt.grid(True)
            plt.tight_layout()
            plt.show()
        except Exception as e:
            messagebox.showerror("错误", f"统计失败: {str(e)}")

    def on_row_double_click(self, event):
        item = self.tree.selection()[0]
        index = self.tree.index(item)
        record = self.records[index]

        details_window = tk.Toplevel()
        details_window.title(f"帧 {record.FrameNumber} 详情")
        details_window.geometry("1000x600")

        paned = tk.PanedWindow(details_window, orient=tk.VERTICAL)
        paned.pack(fill="both", expand=True)

        # 区域1: 目标 TGT 列表
        frame_targets = ttk.Labelframe(paned, text="目标列表 (targets)")
        tree_targets = ttk.Treeview(frame_targets, columns=("ID", "Lat", "Lon", "Alt", "RelAlt", "TCAS Tag"), show="headings", height=10)
        for col in tree_targets["columns"]:
            tree_targets.heading(col, text=col)
        for tgt in record.targets[:record.targets_num]:
            tree_targets.insert("", tk.END, values=(tgt.target_unique_id, round(tgt.lat, 6), round(tgt.lon, 6), int(tgt.alt), int(tgt.relative_alt), int(tgt.flags.tcas_tag)))
        tree_targets.pack(fill="both", expand=True)
        paned.add(frame_targets)

        # 区域2: 本机状态和故障字
        frame_ownship = ttk.Labelframe(paned, text="本机状态 (ownship_status_infomation)")
        status_text = tk.Text(frame_ownship, height=8)
        status_text.insert("end", f"Sensitivity Level: {record.ownship_status_infomation.Sensitivity_Level_Control}\n")
        status_text.insert("end", f"ADSB OUT Mode: {record.ownship_status_infomation.ADSB_OUT_mode}\n")
        status_text.insert("end", f"ADSB IN Mode: {record.ownship_status_infomation.ADSB_IN_mode}\n")
        status_text.insert("end", f"SW Number: {record.ownship_status_infomation.sw_number}\n")
        status_text.insert("end", "\n故障位:\n")
        for k, v in record.ownship_status_infomation.ADSB_Fault_Status.items():
            status_text.insert("end", f"{k}: {'故障' if v else '正常'}\n")
        status_text.config(state="disabled")
        status_text.pack(fill="both", expand=True)
        paned.add(frame_ownship)

        # 区域3: ADS-B 原始消息
        frame_adsb = ttk.Labelframe(paned, text="ADS-B 原始消息 (targets_adsbMsg)")
        tree_adsb = ttk.Treeview(frame_adsb, columns=("Year", "Month", "Day", "Hour", "Min", "Sec", "Ant", "Level"), show="headings", height=8)
        for col in tree_adsb["columns"]:
            tree_adsb.heading(col, text=col)
        for msg in record.targets_adsbMsg[:record.ADSB_MSG_Validity_Num]:
            ts = msg.timestamp
            tree_adsb.insert("", tk.END, values=(ts.year, ts.month, ts.day, ts.hour, ts.minute_high * 8 + ts.minute_low, ts.second, msg.receiveAntennas.receive, msg.signLevel))
        tree_adsb.pack(fill="both", expand=True)
        paned.add(frame_adsb)

def center_window(root, width=1920, height=1080):
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    x = int((screen_width - width) / 2)
    y = int((screen_height - height) / 2)
    root.geometry(f"{width}x{height}+{x}+{y}")

if __name__ == "__main__":
    # 放在Tk初始化前设置DPI感知
    import ctypes

    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(1)
    except:
        pass

    root = tk.Tk()  # 第一步：创建窗口
    center_window(root) # 第二步：设置大小并居中
    app = ADSBGui(root)  # 第三步：加载你的主界面类
    root.mainloop() # 第四步：进入事件循环

